package com.waterstudio.filedownloader.ext.m3u8.utils;

import android.net.Uri;
import android.support.annotation.NonNull;

import com.iheartradio.m3u8.Encoding;
import com.iheartradio.m3u8.Format;
import com.iheartradio.m3u8.ParsingMode;
import com.iheartradio.m3u8.PlaylistParser;
import com.iheartradio.m3u8.data.EncryptionData;
import com.iheartradio.m3u8.data.EncryptionMethod;
import com.iheartradio.m3u8.data.MasterPlaylist;
import com.iheartradio.m3u8.data.MediaPlaylist;
import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.PlaylistData;
import com.iheartradio.m3u8.data.StreamInfo;
import com.iheartradio.m3u8.data.TrackData;
import com.waterstudio.filedownloader.ext.m3u8.bean.EncKey;
import com.waterstudio.filedownloader.ext.m3u8.bean.M3U8;
import com.waterstudio.filedownloader.ext.m3u8.bean.M3U8Ts;
import com.waterstudio.filedownloader.ext.m3u8.bean.MasterList;
import com.waterstudio.filedownloader.ext.m3u8.client.Client;
import com.waterstudio.filedownloader.ext.m3u8.client.ClientBuilder;

import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.exception.UnSupportEncryptionException;

/**
 * Created by chensuilun on 2019/3/20.
 */
public class M3u8Parser {
    /**
     * @param url  当前 M3U8 文件的路径
     * @param file 当前 M3U8 文件
     * @throws Exception
     */
    public static M3U8 parse(String url, File file) throws Exception {
        try (InputStream inputStream = new FileInputStream(file)) {
            M3U8 m3U8 = parseM3u8(url, inputStream);
            m3U8.setM3U8FilePath(file.getAbsolutePath());
            return m3U8;
        }
    }

    private static M3U8 parseM3u8(String url, InputStream inputStream) throws Exception {
        String baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
        M3U8 m3U8 = new M3U8(baseUrl);
        Playlist parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT).parse();
        Client client;
        if (parser.hasMediaPlaylist()) {
            MediaPlaylist mediaPlaylist = parser.getMediaPlaylist();
            int i = 0;
            M3U8Ts m3U8Ts;
            Set<String> added = new HashSet<String>();
            for (TrackData track : mediaPlaylist.getTracks()) {
                String tsUrl = concatUriList(baseUrl, track.getUri());
                if (added.contains(tsUrl)) {
                    //avoid #EXT-X-BYTERANGE
                    continue;
                }
                added.add(tsUrl);
                float duration = track.getTrackInfo().duration;
                m3U8Ts = new M3U8Ts(tsUrl, duration);
                if (track.isEncrypted()) {
                    if (track.getEncryptionData().getMethod() == EncryptionMethod.SAMPLE_AES) {
                        throw new UnSupportEncryptionException(track.getEncryptionData().getMethod().toString());
                    }
                    client = ClientBuilder.newClient(Uri.parse(concatUriList(baseUrl, track.getEncryptionData().getUri())));
                    try {
                        EncKey encKey = parseKey(client, track.getEncryptionData(), i);
                        m3U8Ts.setEncKey(encKey);
                        m3U8.addTs(m3U8Ts);
                    } finally {
                        client.close();
                    }
                } else {
                    m3U8.addTs(m3U8Ts);
                }
                i++;
            }
        } else if (parser.hasMasterPlaylist()) {
            MasterPlaylist masterPlaylist = parser.getMasterPlaylist();
            MasterList masterList;
            for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
                String m3u8Url = concatUriList(baseUrl, playlist.getUri());
                StreamInfo streamInfo = playlist.getStreamInfo();
                int bandwidth = streamInfo.getBandwidth();
                masterList = new MasterList(m3u8Url, bandwidth);
                client = ClientBuilder.newClient(Uri.parse(m3u8Url));
                try {
                    client.connect();
                    masterList.setM3U8(parseM3u8(m3u8Url, client.getInputStream()));
                    m3U8.addMaster(masterList);
                } finally {
                    client.close();
                }
            }
        }
        return m3U8;
    }


    public static String concatUriList(@NonNull String host, @NonNull String str) {
        if (str.startsWith("http")) {
            return str;
        } else if (str.startsWith("//")) {
            return "http:".concat(str);
        } else {
            return host.concat(str);
        }
    }


    @NonNull
    private static EncKey parseKey(@NonNull Client client, @NonNull EncryptionData encryptionData, int index) throws Exception {
        byte[] vector;
        byte[] key = loadBin(client);
        if (encryptionData.hasInitializationVector()) {
            List<Byte> initializationVector = encryptionData.getInitializationVector();
            vector = toByteArray(initializationVector);
        } else {
            vector = toByteArray16(index);
        }
        return new EncKey(key, vector);
    }

    @NonNull
    private static byte[] toByteArray(@NonNull Collection<Byte> collection) {
        byte[] bArr = new byte[collection.size()];
        int i = 0;
        for (Number byteValue : collection) {
            int i2 = i + 1;
            bArr[i] = byteValue.byteValue();
            i = i2;
        }
        return bArr;
    }


    @NonNull
    private static byte[] loadBin(@NonNull Client client) throws Exception {
        client.connect();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        IOUtils.copy(client.getInputStream(), byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    }


    private static byte[] toByteArray16(int i) {
        return new byte[]{(byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) ((i >> 8) & 255), (byte) (i & 255)};
    }


}
